
open Chipmunk.Low_level

type t = 
	{           name           : string
	  ;         body           : cpBody
	  ; mutable propellerState : bool array
	  ; mutable color          : Types.color
	  ; mutable weapon         : string
	  ; mutable hp             : float
	  ; mutable frags          : int
	  ; mutable deaths         : int
     ; mutable crosshair      : float
	}

(* for determining who caused damage/death *)
type optT = t option

let radius = Config.cellSize /. 2.0

let create space id = 
	(* create body with given mass and infinite momentum *)
    let body = cpBodyNew Config.playerMass infinity in
	(* initial position on the map *)
    let () = cpBodySetPos ~body ~p:(cpv ~x:!(Config.spawnX) ~y:!(Config.spawnY)) in
	(* put the player in the space *)
    let () = cpSpaceAddBody ~space ~body in
	(* save the player id in the body *)
	let () = cpBodySetData body id in
	(* create shape for the player *)
    let shape = cpCircleShapeNew ~body ~radius ~offset:(cpvzero()) in
    let () = cpShapeSetElasticity ~shape ~e:0.1 in
    let () = cpShapeSetFriction ~shape ~u:0.5 in
	(* set collision type for collision handler *)
    let () = cpShapeSetCollisionType ~shape ~collision_type:1 in
    let () = cpSpaceAddShape ~space ~shape in
	(* the result *)
	let propellerState = Array.make 4 false in
	let color = Types.green in
	let name = string_of_int id and weapon = "simple"
	and hp = !(Config.startHP) and frags = 0 and deaths = 0 and crosshair = 1.0
   in
   { name; body; propellerState; color; weapon; hp; frags; deaths; crosshair; }

let show player = 
	let pos = cpBodyGetPos player.body in
	let (r, g, b, a) = player.color in
	Types.Data.List [
		Types.Data.Float pos.cp_x;
		Types.Data.Float pos.cp_y;
		Types.Data.Float r;
		Types.Data.Float g;
		Types.Data.Float b;
      Types.Data.Float a;
      Types.Data.Float player.hp]

let getScore player =
  Types.Data.List [
    Types.Data.String player.name;
    Types.Data.Int    player.frags;
    Types.Data.Int    player.deaths]

let drawCircle ~center:{cp_x=x; cp_y=y} ~r ~a ~color =
	let pi = 4.0 *. atan 1.0 in
	let segs = 15 in
	let coef = 2.0 *. pi /. (float segs) in
	let () = GL.glBegin GL.GL_LINE_STRIP in
	let () = GL.glColor4v color in
	let () = for n = 0 to segs do
		let rads = (float n) *. coef in
		GL.glVertex2
			(r *. cos(rads +. a) +. x)
			(r *. sin(rads +. a) +. y)
	done in
	let () = GL.glVertex2 x y in
	GL.glEnd()

let draw data = match data with
	| Types.Data.List [
		Types.Data.Float x;
		Types.Data.Float y;
		Types.Data.Float r;
		Types.Data.Float g;
		Types.Data.Float b;
      Types.Data.Float a;
      Types.Data.Float hp] ->
			let color = (r, g, b, a) in
			drawCircle	(cpv x y) radius 0.0 color
	| _ -> assert false

let step player =
	let apply force = 
		cpBodyApplyImpulse player.body force (cpvzero())
   and mvFc = !Config.moveForce 
   and curFc = cpBodyGetForce player.body 
	in begin
		if (player.propellerState.(0)) && (curFc.cp_x<mvFc)     then 
         apply (cpv ~x:0.0   ~y:mvFc);
		if (player.propellerState.(1)) && (curFc.cp_x>(-.mvFc)) then 
         apply (cpv ~x:(-.mvFc) ~y:0.0);
		if (player.propellerState.(2)) && (curFc.cp_y<mvFc)     then apply (cpv ~x:mvFc  ~y:0.0);
		if (player.propellerState.(3)) && (curFc.cp_y>(-.mvFc)) then apply (cpv ~x:0.0   ~y:(-.mvFc));
	end

(* this is just demonstration, signatures of collisionBegin
 * and collisionSeparate probably will be different *)
let collisionBegin ge player = 
	let () = player.color <- Types.red in
	()
let collisionSeparate ge player = 
	(* this is incorrect if the player collides with
	 * more than one other player, but it is just a demonstration *)
	let () = player.color <- Types.green in
	()

(* functions for triggers *)
(* args: return value (true/false/unit); player that triggered the trigger *)
let jump player params = match params with
   | Types.Data.Float force -> cpBodyApplyImpulse player.body (cpv 0.0 force) (cpvzero())
   | _ -> assert false
let makeBlue player _ = player.color <- Types.blue
let makeGreen player _ = player.color <- Types.green
let makeCyan player _ = player.color <- Types.cyan
	
let command player cmd = 
	let keys = player.propellerState in
	match cmd with
   | Types.Data.String "+Up"    -> let () = keys.(0) <- true  in Types.Data.List[]
	| Types.Data.String "-Up"    -> let () = keys.(0) <- false in Types.Data.List[]
   | Types.Data.String "+Left"  -> let () = keys.(1) <- true; 
                                   player.crosshair <- -.1.0 in Types.Data.List[]
	| Types.Data.String "-Left"  -> let () = keys.(1) <- false in Types.Data.List[]
   | Types.Data.String "+Right" -> let () = keys.(2) <- true; 
                                   player.crosshair <- 1.0 in Types.Data.List[]
	| Types.Data.String "-Right" -> let () = keys.(2) <- false in Types.Data.List[]
	| Types.Data.String "+Down"  -> let () = keys.(3) <- true  in Types.Data.List[]
	| Types.Data.String "-Down"  -> let () = keys.(3) <- false in Types.Data.List[]
	| Types.Data.String "status" -> Types.Data.Float player.hp
	| _ -> Types.Data.List [Types.Data.String "Unknown command"; cmd]

(* actions taken when player dies *)
(* [charged : t Option] is the optional death cause *)
let die player charged =
   let () = player.deaths <- player.deaths + 1 in
   (* ``careful man, this one is surely some killer...'' *)
   let () = (match charged with Some killer -> killer.frags <- killer.frags + 1 
   (* ``- why did he die? - oh, it was nothing...'') *)
               | None -> ()) in
   let () = player.hp <- !Config.startHP in
   let () = cpBodySetPos ~body:(player.body) ~p:(cpv ~x:!(Config.spawnX) ~y:!(Config.spawnY)) in
   (* TODO: fancy-printf "YOU DIE", respawn delay *)
   ()

(* damages player. Heals when [value] is negative. *)
let damage value player charged =
   if player.hp -. value <= 0. then die player charged
   else if player.hp -. value > !Config.maxHP
   then player.hp <- !Config.maxHP
   else player.hp <- player.hp -. value

(* damage function to be used with triggers *)
let triggerDamage player params = match params with
   (* None -- triggers can't be charged of murder (yet) *)
   (* ``don't blame him, he's only a poor little trigger...'' *)
   | Types.Data.Float value -> damage value player None
   | _ -> assert false
